#region License

// Copyright (c) 2008, BXO Solutions AB
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without 
// modification, are permitted provided that the following conditions are met:
// 
//     * Redistributions of source code must retain the above copyright notice, 
//       this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright 
//       notice, this list of conditions and the following disclaimer in the 
//       documentation and/or other materials provided with the distribution.
//     * Neither the name of the BXO Solutions AB nor the names of its 
//       contributors may be used to endorse or promote products derived from 
//       this software without specific prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE 
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#endregion

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Castle.ActiveRecord;

namespace Bxo.TestingPortal.Entities {
    /// <summary>
    /// Unit that allows organizing scenarios.
    /// </summary>
    [ActiveRecord]
    public class Category {
        private string name;

        private Project project;

        private IList<Scenario> scenarios;

        /// <summary>
        /// This member is provided only to satisfy NHibernate requirements and should not be used
        /// by user code.
        /// </summary>
        protected Category() {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Category"/> class
        /// with the specified name.
        /// </summary>
        /// <param name="name">Name for the category.</param>
        /// <exception cref="ArgumentNullException"><paramref name="name"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentException"><paramref name="name"/> is <see cref="string.Empty"/>.</exception>
        public Category(string name) {
            if (name == null) {
                throw new ArgumentNullException("name");
            }
            if (name.Length == 0) {
                throw new ArgumentException("Category name must not be empty.", "name");
            }

            this.name = name;
            this.scenarios = new List<Scenario>();
        }

        /// <summary>
        /// Gets or sets the unique identifier for the category.
        /// </summary>
        /// <value>
        /// Identifier for the category.
        /// </value>
        /// <remarks>
        /// <para>Do not change the value of this property unless you know what you are doing.</para>
        /// </remarks>
        [PrimaryKey]
        public virtual int Id { get; set; }

        /// <summary>
        /// Gets the project the category belongs to.
        /// </summary>
        /// <value>
        /// <para>Project that the category belongs to or <see langword="null"/> if the
        /// category does not belong to any project.</para>
        /// </value>
        [BelongsTo(Cascade = CascadeEnum.SaveUpdate)]
        public virtual Project Project {
            get { return this.project; }
            internal set { this.project = value; }
        }

        /// <summary>
        /// Gets or sets the name of the category.
        /// </summary>
        /// <value>
        /// <para>Name for the category.</para>
        /// </value>
        /// <exception cref="ArgumentNullException"><paramref name="value"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentException"><paramref name="value"/> is <see cref="string.Empty"/>.</exception>
        [Property]
        public virtual string Name {
            get { return this.name; }
            set {
                if (value == null) {
                    throw new ArgumentNullException("value");
                }
                if (value.Length == 0) {
                    throw new ArgumentException("Category name must not be empty.", "value");
                }

                this.name = value;
            }
        }

        /// <summary>
        /// Gets the list of scenarios that are assigned to the category.
        /// </summary>
        /// <value>
        /// List of scenarios in this category.
        /// </value>
        [HasAndBelongsToMany(Access = PropertyAccess.FieldCamelcase, Cascade = ManyRelationCascadeEnum.SaveUpdate, Inverse = true, Table = "ScenarioCategoryAssociations", ColumnKey = "Category", ColumnRef = "Scenario")]
        public virtual ICollection<Scenario> Scenarios {
            get { return new ScenarioCollection(this.scenarios, this); }
        }

        public override string ToString() {
            return this.name;
        }

        #region Nested type: ScenarioCollection

        private class ScenarioCollection : Collection<Scenario> {
            private readonly Category owner;

            public ScenarioCollection(IList<Scenario> innerList, Category owner) : base(innerList) {
                this.owner = owner;
            }

            protected override void ClearItems() {
                // Use copy of the wrapped collection because the original collection is modified
                // with every scenario removal (which would invalidate the enumerator otherwise).
                var copy = new List<Scenario>(this);
                foreach (var scenario in copy) {
                    scenario.Categories.Remove(this.owner);
                }
            }

            protected override void InsertItem(int index, Scenario item) {
                base.InsertItem(index, item);

                // Check if the item is already added is needed to prevent infinite loop.
                if (!item.Categories.Contains(this.owner)) {
                    item.Categories.Add(this.owner);
                }
            }

            protected override void RemoveItem(int index) {
                Scenario item = this[index];

                base.RemoveItem(index);

                item.Categories.Remove(this.owner);
            }

            protected override void SetItem(int index, Scenario item) {
                throw new NotSupportedException();
            }
        }

        #endregion
    }
}